#include "nemu.h"
#include "device/mmio.h"

#define PMEM_SIZE (128 * 1024 * 1024)

#define pmem_rw(addr, type) *(type *)({\
    Assert(addr < PMEM_SIZE, "physical address(0x%08x) is out of bound", addr); \
    guest_to_host(addr); \
    })

uint8_t pmem[PMEM_SIZE];

/* Memory accessing interfaces */

uint32_t paddr_read(paddr_t addr, int len) {
  int mmio_n;
  if ((mmio_n = is_mmio(addr)) != -1)
    return mmio_read(addr, len, mmio_n);
  else
    return pmem_rw(addr, uint32_t) & (~0u >> ((4 - len) << 3));
}

void paddr_write(paddr_t addr, int len, uint32_t data) {
  int mmio_n;
  if ((mmio_n = is_mmio(addr)) != -1)
    mmio_write(addr, len, data, mmio_n);
  else
    memcpy(guest_to_host(addr), &data, len);
}

paddr_t page_translate(vaddr_t vaddr, bool w) {
  PDE pde;
  PTE pte;
  uint32_t DIR=(vaddr>>22);
  uint32_t PDE_addr=(cpu.cr3.page_directory_base<<12)+(DIR<<2);
  pde.val=paddr_read(PDE_addr, 4);
  assert(pde.present);
  uint32_t PAGE=((vaddr>>12)&0x3ff);
  uint32_t PTE_addr=(pde.val&0xfffff000)+(PAGE<<2);
  pte.val=paddr_read(PTE_addr, 4);
  assert(pte.present);
  uint32_t phaddr=(pde.val&0xfffff000)+(vaddr&0xfff);
  pde.accessed=1;
  paddr_write(PDE_addr, 4, pde.val);
  if(pte.accessed==0 || (pte.dirty==0 && w) ){
    pte.accessed=1;
    pte.dirty=1;
  }
  paddr_write(PTE_addr, 4, pte.val);
  return phaddr;
}

uint32_t vaddr_read(vaddr_t addr, int len) {
    if(cpu.cvimr0.paging) {
        if ((((addr<<20)>>20)+len)>0x1000) {
            int flen, slen;
            flen=0x1000-(addr&0xfff);
            slen=len-flen;
            uint32_t faddr=page_translate(addr, false);
            uint32_t fmen=paddr_read(faddr, flen);
            uint32_t saddr=page_translate(addr+flen, false);
            uint32_t smen=paddr_read(saddr, slen);
            return fmen+(smen<<(flen<<3));
        }
        else {
            paddr_t paddr = page_translate(addr, false);
            return paddr_read(paddr, len);
        }
    }
    else
        return paddr_read(addr, len);
}

void vaddr_write(vaddr_t addr, int len, uint32_t data) {
  if(cpu.cr0.paging) {
        if ((((addr<<20)>>20)+len)>0x1000) {
            int flen, slen;
            flen=0x1000-(addr&0xfff);
            slen=len-flen;
            uint32_t faddr=page_translate(addr, true);
            paddr_write(faddr, flen, data);
            uint32_t hdata=data>>(flen<<3);
            uint32_t saddr=page_translate(addr+flen, true);
            paddr_write(saddr, slen, hdata);
        }
        else {
            paddr_t paddr = page_translate(addr, true);
            paddr_write(paddr, len, data);
        }
    }
    else
        paddr_write(addr, len, data);
}
